package my.study.demo.proxy.other.study;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//public $Proxy0 extends Proxy implements ...{}
//InvocationHandler对象就是一个管理类，保存了被代理的对象以及调用被代理对象的反射调用方法invoke()
//而整个代理过程代理对象调用一个方法的到时候，最终必须要调用到被代理类的这个方法
//能调用到被代理的方法需要有当时被代理对象以及方法， 所以在代理类重要保存h对象，h对象就是干这个的。
//但是实际上h对象即这个管理对象并没有保存在代理类自己中，而是在Proxy类中有一个属性是InvocationHandler h
//通过让代理类继承Proxy类，然后在实例化的时候初始化父类的h对象属性就可以了。
//h对象作用很重要，所以必须要保存，jdk中是放在Proxy中再去继承他，这就导致了代理类无法再去继承其他的类，因为java是单一继承的
//因为单一继承的的原因，jdk的动态代理注定不能去代理已经继承其他类的类，只能代理实现了接口的类

//针对只能代理实现了的接口的实现类的详细解释如下(部分是个人理解)；
//首先代理类调用的方法名一定要和被代理的方法名一致，这样给用户端的感觉才是好像我就是在调用我的这个类一样，实际上是调用代理类。
//实现代理的大体思路就是两种，继承被代理类/实现被代理类实现的接口，因为代理类和被代理类要是同一类型的，这两种思路都是同一类型的
//比如第二种思路，代理类和被代理类都实现了同一个接口，也就可以说他们同是接口对应的类型的，这样做是有原因的。
//因为管理类中getInstance方法返回参数就是代理类，返回值肯定是Object类型，所以肯定会牵扯到类型的向下转换
//如果不是实现同一个接口，你根本就不知道代理类是什么类型，就没办法把Object转换成代理类的类型，所以干脆就实现同一个接口
//这样在调用getInstance接口接返回值的时候直接强转成接口类型就行IPerson person = (IPerson)JDKProxy.getInstance();
//这就是面向接口编程的好处，所以一定要让代理类和被代理类的类型是可以统一的，能实现这种统一效果只能继承或者实现同一个接口。

//当然jdk动态代理中如果不把管理类对象h放到Proxy保存的话，而是放到代理类自身中保存也是可以的，这样实现的话就可以不需要继承Proxy了（Proxy最大作用就是保存h）
//这样就不是一定得是实现接口了。因为不会出现单一继承的问题了

//达到统一类型的目的：
//1.代理类也实现被代理类的接口，向上转化的话就都是接口类型
//2、被代理类继承了一类，为了达到统一类型的目的，要么被代理类继承代理类，这是cglib的做法，要么被代理类也去结成代理类继承的类
    //因为写死了代理类首先肯定要继承$Proxy0，此处为了达到统一类型的目的，就违反了单一继承的原则。
//详细解释一下为什么一定要统一类型，因为客户端使用动态代理，他根本不关心动态代理的具体实现细节，不会去关心代理类是什么类型，
    //调用代理类的哪个方法才能调用到我想要调用的那个方法，不会关心我调用一个方法，你是怎么通过动态代理一步步调用到我被代理类的方法的，
    //外观看起来好像就是调用了同一个方法，所以必须要是一个类型。
    //也就是说你被代理类有的方法我动态代理类也必须有这个方法，被代理类的东西代理类都要有，才能保证随意调用一个方法，代理类都能调用到。
public class JDKProxy implements InvocationHandler {

    private Object target;

    //根据被代理对象获得代理对象
    public Object getInstance(Object target) {
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        method.invoke(this.target, args);
        return null;
    }
}
